-- Networking unit tests

require 'networking'
require 'util'
require 'threads'

local server = nil
local client = nil
local networkScheduler = Scheduler:create()

local address, port = "127.0.0.1", 1234

-- create an object that serves as a monitor for the network
local netMonitor = Object:create()


local function create_server()
    server = Server:create()
    networkScheduler:addTask(server)
    server:bind("*", port)
    server:startListening()
    server:setDelegate(netMonitor)
    return true
end

local function get_server_info()
    local info = server:getInfo()
    if info then
        return true
    else
        return false
    end
end

local function create_client()
    client = Client:create()
    client:connect(address, port)
    networkScheduler:addTask(client)
    client:setDelegate(netMonitor)
    return true
end

local function accept_connection()
    
    -- start up a scheduler containing the network object and a monitor
    -- when a single client connects, the monitor makes the network stop
    -- and aborts operation
    
    function netMonitor:main()
        while server.connectedClients == 0 do
            yield()
        end
        networkScheduler:stop() -- abort operation
    end

    networkScheduler:addTask(netMonitor)
    
    info = server:getInfo()
    
    --print("Waiting for connection on port " .. info.port)
    networkScheduler:start()
    
    return true
end

local function send_data_to_client()

	-- send a simple ping
	local data = {command=NetworkCommands.ping}
	
	assert(server.connectedClients > 0)
	server:sendToAllClients(data)
	
	local clientReceivedData = false
	
	-- when data is received, call netMonitor:networkDataReceived
	function netMonitor:networkDataReceived (data, peer)
		if (data.command == NetworkCommands.ping) then 
			clientReceivedData = true
		else 
			error("Client got some data it didn't expect")
		end
	end
		
	function netMonitor:main()
		while clientReceivedData == false do
			yield()
		end
		networkScheduler:stop()
	end
	
	networkScheduler:addTask(netMonitor)
	
	-- the monitor will now wait for the client to receive and process the data
	networkScheduler:start()
	
	return true
	
end

local function disconnect_client()
    client:disconnect()
    
    function netMonitor:main()
        while server.connectedClients > 0 do
            yield()
        end
        networkScheduler:stop()
    end
    
    networkScheduler:addTask(netMonitor)
    networkScheduler:start()
    
    return true
end
    
    
local function reconnect_and_kick_client()
    -- re-use the connect test to bring back a client
    client:connect(address, port)
    
    netMonitor.networkDataReceived = nil
    
    accept_connection()
    
    local c = server.clients[1]
    
    server:disconnectClient(c, "Kicked! Hah!")
    
    function netMonitor:main()
        while server.connectedClients > 0 do
            yield()
        end
        networkScheduler:stop()
    end
    
    networkScheduler:addTask(netMonitor)
    networkScheduler:start()
    
    return true
end

if tests == nil then tests = {} end

tests.networking = {
    create_server = {
        description = "Creates a server object that listens on port 1234",
        test = create_server,
        prerequisites = {}
    },
    
    create_client = {
        description = "Creates a client object that connects to localhost:1234",
        test = create_client,
        prerequisites = {"create_server"}        
    },
    
    get_server_info = {
        description = "Displays information on a set-up server object",
        test = get_server_info,
        prerequisites = {"create_server"}        
    },
    
    accept_connection = {
        description = "Waits for a single client to connect.",
        test = accept_connection,
        prerequisites = {"create_server", "create_client"}        
    },
    
    send_data_to_client = {
    	description = "Sends a table to a client.",
    	test = send_data_to_client,
    	prerequisites = {"accept_connection"},
    },
    
    disconnect_client = {
        description = "Makes a client send a disconnect notification",
        test = disconnect_client,
        prerequisites = {"accept_connection", "send_data_to_client"}
    },
    
    reconnect_and_kick_client = {
        description = "Reconnects and then kicks a client",
        test = reconnect_and_kick_client,
        prerequisites = {"disconnect_client"}
    }
}
